-
Notifications
You must be signed in to change notification settings - Fork 23
Add Confirmation Dialog System #263
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Co-authored-by: Devasy <110348311+Devasy@users.noreply.github.com>
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
✅ Deploy Preview for split-but-wiser ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
WalkthroughThis PR introduces a custom Confirmation Dialog System to replace native Changes
Suggested reviewers
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@web/components/ui/ConfirmDialog.tsx`:
- Around line 50-57: The icon container in ConfirmDialog currently uses
hardcoded Tailwind classes (`bg-red-100`, `bg-blue-100`, `text-red-600`,
`text-blue-600`) which don't respect the app theme; update the ConfirmDialog
component to call useTheme() (same hook used by Button/Modal), derive
theme-aware background/text classes based on isDanger and the returned theme
value, and apply those computed classes to the icon container div (the element
that currently builds className with isDanger). Ensure the logic handles both
danger/normal states and maps to the theme's glass/neobrutalism class tokens so
the AlertTriangle/Info icon colors adapt with the rest of the UI.
- Around line 30-48: The Modal usage in ConfirmDialog.tsx is missing alert
dialog ARIA attributes and safe autofocus behavior: pass role="alertdialog" and
aria-describedby="<unique-id>" to the dialog container (either via Modal props
or by wrapping its content in a div with those attributes) and ensure the dialog
message element has the matching id so screen readers associate the description;
also change autofocus so when isDanger is true the cancel Button gets autoFocus
(to avoid accidental destructive confirms) and otherwise the confirm Button
retains autoFocus; update references to onCancel/onConfirm, isDanger,
cancelText, confirmText, title and Modal accordingly.
In `@web/contexts/ConfirmContext.tsx`:
- Around line 43-51: handleConfirm and handleCancel resolve the stored resolver
(resolvePromise) but leave it in state; after calling resolvePromise(true/false)
also clear it to avoid stale resolver references—update both handlers
(handleConfirm, handleCancel) to call setIsOpen(false) and then
setResolvePromise(null) (or undefined) after invoking resolvePromise, ensuring
resolvePromise is referenced safely before call and cleared afterward for
cleaner state management.
- Around line 35-41: The confirm() function can overwrite an existing
setResolvePromise when called while a dialog is open, leaving the prior Promise
unresolved; update confirm (referencing confirm, isOpen, setIsOpen, setOptions,
setResolvePromise) to first handle any existing pending promise (call the
previous resolve or reject to settle it and clear setResolvePromise) or
early-return when isOpen is true (e.g., resolve false or throw), then proceed to
setOptions, setIsOpen, and setResolvePromise for the new dialog; also add isOpen
to confirm's useCallback dependencies to avoid stale state.
| return ( | ||
| <Modal | ||
| isOpen={isOpen} | ||
| onClose={onCancel} | ||
| title={title} | ||
| footer={ | ||
| <> | ||
| <Button variant="ghost" onClick={onCancel}> | ||
| {cancelText} | ||
| </Button> | ||
| <Button | ||
| variant={isDanger ? 'danger' : 'primary'} | ||
| onClick={onConfirm} | ||
| autoFocus | ||
| > | ||
| {confirmText} | ||
| </Button> | ||
| </> | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing role="alertdialog" and aria-describedby for accessibility.
The PR objectives state accessibility support via role="alertdialog", but this isn't implemented. For alert dialogs, the ARIA role should be set on the dialog container, and focus management should trap focus within the dialog. Additionally, consider placing autoFocus on the cancel button for danger variants to prevent accidental destructive actions.
,
Suggested accessibility improvements
The Modal component should receive ARIA attributes. If Modal doesn't support passing these through, consider wrapping the content or enhancing Modal. At minimum, for alert dialogs:
<Modal
isOpen={isOpen}
onClose={onCancel}
title={title}
+ role="alertdialog"
+ aria-describedby="confirm-dialog-message"
footer={
<>
<Button variant="ghost" onClick={onCancel}>
{cancelText}
</Button>
<Button
variant={isDanger ? 'danger' : 'primary'}
onClick={onConfirm}
- autoFocus
+ autoFocus={!isDanger}
>
{confirmText}
</Button>
</>
}
>And add the id to the message:
- <p className="text-base opacity-90 leading-relaxed">{message}</p>
+ <p id="confirm-dialog-message" className="text-base opacity-90 leading-relaxed">{message}</p>🤖 Prompt for AI Agents
In `@web/components/ui/ConfirmDialog.tsx` around lines 30 - 48, The Modal usage in
ConfirmDialog.tsx is missing alert dialog ARIA attributes and safe autofocus
behavior: pass role="alertdialog" and aria-describedby="<unique-id>" to the
dialog container (either via Modal props or by wrapping its content in a div
with those attributes) and ensure the dialog message element has the matching id
so screen readers associate the description; also change autofocus so when
isDanger is true the cancel Button gets autoFocus (to avoid accidental
destructive confirms) and otherwise the confirm Button retains autoFocus; update
references to onCancel/onConfirm, isDanger, cancelText, confirmText, title and
Modal accordingly.
| <div className="flex items-start gap-4"> | ||
| <div | ||
| className={`p-3 rounded-full flex-shrink-0 ${ | ||
| isDanger ? 'bg-red-100 text-red-600' : 'bg-blue-100 text-blue-600' | ||
| }`} | ||
| > | ||
| {isDanger ? <AlertTriangle size={24} /> : <Info size={24} />} | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Icon container styling doesn't adapt to themes.
The icon background uses hardcoded colors (bg-red-100, bg-blue-100) that don't integrate with the Glassmorphism/Neobrutalism theme system. Both Button and Modal components use useTheme() to adapt styling. For visual consistency, this component should also adapt.
Theme-aware icon styling example
+import { useTheme } from '../../contexts/ThemeContext';
+import { THEMES } from '../../contexts/ThemeContext';
...
+ const { style } = useTheme();
+
+ const iconContainerStyles = style === THEMES.NEOBRUTALISM
+ ? isDanger
+ ? 'bg-red-500 text-white border-2 border-black'
+ : 'bg-blue-500 text-white border-2 border-black'
+ : isDanger
+ ? 'bg-red-100 text-red-600'
+ : 'bg-blue-100 text-blue-600';
...
<div
- className={`p-3 rounded-full flex-shrink-0 ${
- isDanger ? 'bg-red-100 text-red-600' : 'bg-blue-100 text-blue-600'
- }`}
+ className={`p-3 rounded-full flex-shrink-0 ${iconContainerStyles}`}
>🤖 Prompt for AI Agents
In `@web/components/ui/ConfirmDialog.tsx` around lines 50 - 57, The icon container
in ConfirmDialog currently uses hardcoded Tailwind classes (`bg-red-100`,
`bg-blue-100`, `text-red-600`, `text-blue-600`) which don't respect the app
theme; update the ConfirmDialog component to call useTheme() (same hook used by
Button/Modal), derive theme-aware background/text classes based on isDanger and
the returned theme value, and apply those computed classes to the icon container
div (the element that currently builds className with isDanger). Ensure the
logic handles both danger/normal states and maps to the theme's
glass/neobrutalism class tokens so the AlertTriangle/Info icon colors adapt with
the rest of the UI.
| const confirm = useCallback((options: ConfirmOptions) => { | ||
| setOptions(options); | ||
| setIsOpen(true); | ||
| return new Promise<boolean>((resolve) => { | ||
| setResolvePromise(() => resolve); | ||
| }); | ||
| }, []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concurrent confirm() calls can cause promises to hang indefinitely.
If confirm() is called while a dialog is already open, the previous resolvePromise is overwritten without being resolved. This leaves the prior caller's Promise pending forever, potentially causing UI hangs or memory leaks if the caller is awaiting.
Suggested fix: reject or resolve previous promise before opening new dialog
const confirm = useCallback((options: ConfirmOptions) => {
+ // Resolve any pending promise as cancelled before opening new dialog
+ if (resolvePromise) {
+ resolvePromise(false);
+ }
setOptions(options);
setIsOpen(true);
return new Promise<boolean>((resolve) => {
setResolvePromise(() => resolve);
});
- }, []);
+ }, [resolvePromise]);Alternatively, you could queue confirmations or reject with an error if already open:
const confirm = useCallback((options: ConfirmOptions) => {
if (isOpen) {
return Promise.resolve(false); // or throw new Error('Dialog already open')
}
// ... rest of implementation
}, [isOpen]);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const confirm = useCallback((options: ConfirmOptions) => { | |
| setOptions(options); | |
| setIsOpen(true); | |
| return new Promise<boolean>((resolve) => { | |
| setResolvePromise(() => resolve); | |
| }); | |
| }, []); | |
| const confirm = useCallback((options: ConfirmOptions) => { | |
| // Resolve any pending promise as cancelled before opening new dialog | |
| if (resolvePromise) { | |
| resolvePromise(false); | |
| } | |
| setOptions(options); | |
| setIsOpen(true); | |
| return new Promise<boolean>((resolve) => { | |
| setResolvePromise(() => resolve); | |
| }); | |
| }, [resolvePromise]); |
🤖 Prompt for AI Agents
In `@web/contexts/ConfirmContext.tsx` around lines 35 - 41, The confirm() function
can overwrite an existing setResolvePromise when called while a dialog is open,
leaving the prior Promise unresolved; update confirm (referencing confirm,
isOpen, setIsOpen, setOptions, setResolvePromise) to first handle any existing
pending promise (call the previous resolve or reject to settle it and clear
setResolvePromise) or early-return when isOpen is true (e.g., resolve false or
throw), then proceed to setOptions, setIsOpen, and setResolvePromise for the new
dialog; also add isOpen to confirm's useCallback dependencies to avoid stale
state.
| const handleConfirm = useCallback(() => { | ||
| if (resolvePromise) resolvePromise(true); | ||
| setIsOpen(false); | ||
| }, [resolvePromise]); | ||
|
|
||
| const handleCancel = useCallback(() => { | ||
| if (resolvePromise) resolvePromise(false); | ||
| setIsOpen(false); | ||
| }, [resolvePromise]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider clearing resolvePromise after resolution.
After resolving the promise, resolvePromise remains set to the old resolver function. While this doesn't cause immediate issues because isOpen guards the dialog, clearing it provides cleaner state management and prevents potential edge cases.
Suggested cleanup
const handleConfirm = useCallback(() => {
if (resolvePromise) resolvePromise(true);
setIsOpen(false);
+ setResolvePromise(null);
}, [resolvePromise]);
const handleCancel = useCallback(() => {
if (resolvePromise) resolvePromise(false);
setIsOpen(false);
+ setResolvePromise(null);
}, [resolvePromise]);🤖 Prompt for AI Agents
In `@web/contexts/ConfirmContext.tsx` around lines 43 - 51, handleConfirm and
handleCancel resolve the stored resolver (resolvePromise) but leave it in state;
after calling resolvePromise(true/false) also clear it to avoid stale resolver
references—update both handlers (handleConfirm, handleCancel) to call
setIsOpen(false) and then setResolvePromise(null) (or undefined) after invoking
resolvePromise, ensuring resolvePromise is referenced safely before call and
cleared afterward for cleaner state management.
|
Duplicate PR. |
Understood. Acknowledging that this PR is a duplicate and stopping work on this task. |
window.confirmwith a customConfirmDialogcomponent.ConfirmContextanduseConfirmhook.GroupDetailsfor expense/group deletion and member management.role="alertdialog").PR created automatically by Jules for task 17963041256028180656 started by @Devasy23
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.